package joc.internal.transformer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import joc.customize.Contract;
import joc.customize.ForFieldEvaluateAtBeginningOfAllMethods;
import joc.customize.ForFieldEvaluateAtEndOfAllBehaviors;

public class FieldInvariantTransformer extends AccessibleFieldTransformer {

	public FieldInvariantTransformer(ClassPool pool) {
		super(pool);
	}

	@Override
	public void transform(CtClass clazz, CtField field) throws Exception {
		logger.debug("field " + field.getClass().getName() + "." + field.getName());
		for (Class<? extends Annotation> contractAnnotation : Transformer.getContractMap().keySet()) {
			Annotation annotation = (Annotation) field.getAnnotation(contractAnnotation);
			if (annotation != null) {
				logger.info("field " + field.getClass().getName() + "." + field.getName() + " has contract "
						+ Transformer.getContractMap().get(contractAnnotation).getClass().getName());
				applyContractForField(clazz, field, Transformer.getContractMap().get(contractAnnotation), annotation);
			}
		}
	}

	private void applyContractForField(CtClass clazz, CtField field, Contract contract, Annotation annotation)
			throws Exception {
		for (Method contractMethod : getContractMethodsWithAnnotation(contract,
				ForFieldEvaluateAtBeginningOfAllMethods.class)) {
			for (CtMethod method : clazz.getDeclaredMethods()) {
				if (!Modifier.isStatic(method.getModifiers())) {
					method.insertBefore(getContractCallString(contractMethod, contract, annotation, true, "("
							+ contractMethod.getParameterTypes()[1].getName() + ") (this." + field.getName() + ")"));
					logger.info("inserted " + contract.getClass().getName() + " in beginning of method "
							+ method.getLongName() + " for field " + field.getName());
				}
			}
		}
		for (Method contractMethod : getContractMethodsWithAnnotation(contract,
				ForFieldEvaluateAtEndOfAllBehaviors.class)) {
			for (CtBehavior behavior : clazz.getDeclaredBehaviors()) {
				if (!Modifier.isStatic(behavior.getModifiers())) {
					behavior.insertAfter(getContractCallString(contractMethod, contract, annotation, true, "("
							+ contractMethod.getParameterTypes()[1].getName() + ") (this." + field.getName() + ")"));
					logger.info("inserted " + contract.getClass().getName() + " in end of method "
							+ behavior.getLongName() + " for field " + field.getName());
				}
			}
		}
	}

}
